package dashboard.common.config;


import com.meituan.hbdata.appflow.uitls.JsonUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.configuration2.CompositeConfiguration;
import org.apache.commons.configuration2.EnvironmentConfiguration;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.SystemConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Configurations;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.io.*;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.FilenameFilter;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


/**
 * @author zhaoxiang
 *         on 2019/3/10.
 */
@Slf4j
public final class CustomPropertyPlaceholderConfigurer {

    /**
     * 配置分离基目录
     **/
    private static final String BASE_PATH;

    /**
     * 应用程序在配置分离下面的子目录
     **/
    private String appConfigPath = "";

    /**
     * 要加载的配置文件,多个配置文件用,分隔,不写则加载目录下面的所有.properties文件
     **/
    private String[] configFiles;

    /**
     * 自动动态加载,默认为true
     **/
    private boolean autoReload = false;

    /**
     * properties文件配置项
     **/
    private final CompositeConfiguration props = new CompositeConfiguration();

    /**
     * 配置文件对应的PropertiesConfiguration列表
     **/
    private List<PropertiesConfiguration> propList = new ArrayList<PropertiesConfiguration>(10);

    /**
     * 全局配置分离参数
     **/
    private static final String GLOBAL_CONFIG_PATH = "global.config.path";

    static {
        /**JVM启动参数配置***/
        SystemConfiguration sysConfig = new SystemConfiguration();
        String globalPath = sysConfig.getString(GLOBAL_CONFIG_PATH);
        if (StringUtils.isBlank(globalPath)) {/**默认加载classpath下面的文件**/
            globalPath = Thread.currentThread().getContextClassLoader().getResource("").getFile();
        }
        BASE_PATH = globalPath;
    }

    /**
     * 实例完成对象后,初始化操作
     */
    private void init() {
        /**加入配置JVM的启动参数**/
        props.addConfiguration(new SystemConfiguration());
        /**加入环境变更参数**/
        props.addConfiguration(new EnvironmentConfiguration());
    }


    /**
     * 加载默认的配置文件：
     * 如果加了启动参数:global.config.path,就会加载global.config.path下面所有的.properties配置文件
     * 如果没有,就加载classpath下面所有的.properties配置文件
     *
     * @throws ConfigurationException
     */
    private CustomPropertyPlaceholderConfigurer() throws ConfigurationException {
        this(true, null, "");
    }

    /**
     * 加载默认的配置文件：
     * 如果加了启动参数:global.config.path,就会加载global.config.path下面所有的.properties配置文件
     * 如果没有,就加载classpath下面所有的.properties配置文件
     *
     * @param autoReload 自动动态加载
     * @throws ConfigurationException
     */
    private CustomPropertyPlaceholderConfigurer(boolean autoReload) throws ConfigurationException {
        this(autoReload, null, "");
    }

    /**
     * 加载appConfigPath目录下面的所有的.properties配置文件
     * 加载原理：
     * 如果加了启动参数:global.config.path,就会加载${global.config.path}/appConfigPath 下面所有的.properties配置文件
     * 如果没有,就加载${classpath}/appConfigPath下面所有的.properties配置文件
     *
     * @param autoReload    自动动态加载
     * @param appConfigPath 应用的配置目录
     * @throws ConfigurationException
     */
    private CustomPropertyPlaceholderConfigurer(boolean autoReload, String appConfigPath) throws ConfigurationException {
        this(autoReload, appConfigPath, "");
    }


    /**
     * 加载appConfigPath目录下面的所有的.properties配置文件
     * 加载原理：
     * 如果加了启动参数:global.config.path,就会加载${global.config.path}/appConfigPath 下面所有的.properties配置文件
     * 如果没有,就加载${classpath}/appConfigPath下面所有的.properties配置文件
     *
     * @param appConfigPath 应用的配置目录
     * @throws ConfigurationException
     */
    private CustomPropertyPlaceholderConfigurer(String appConfigPath) throws ConfigurationException {
        this(true, appConfigPath, "");
    }

    /**
     * 加载appConfigPath目录下面指定的configFiles配置文件(多个配置文件用,分隔)
     * 加载原理：
     * 如果加了启动参数:global.config.path,就会加载${global.config.path}/appConfigPath下面configFiles里面的配置文件
     * 如果没有,就加载${classpath}/appConfigPath下面所有的configFiles里面的配置文件
     *
     * @param autoReload    自动动态加载
     * @param appConfigPath 应用的配置目录
     * @param configFiles   多个配置文件用,分隔
     * @throws ConfigurationException
     */
    private CustomPropertyPlaceholderConfigurer(boolean autoReload, String appConfigPath, String configFiles) throws ConfigurationException {
        this(true, appConfigPath, StringUtils.split(configFiles, ","));
    }

    /**
     * 加载appConfigPath目录下面指定的configFiles配置文件(多个配置文件用,分隔)
     * 加载原理：
     * 如果加了启动参数:global.config.path,就会加载${global.config.path}/appConfigPath下面configFiles[]里面的配置文件
     * 如果没有,就加载${classpath}/appConfigPath下面所有的configFiles[]里面的配置文件
     *
     * @param appConfigPath 应用的配置目录
     * @param configFiles   配置文件数组
     * @throws ConfigurationException
     */
    private CustomPropertyPlaceholderConfigurer(String appConfigPath, String[] configFiles) throws ConfigurationException {
        this(true, appConfigPath, configFiles);
    }


    /**
     * 加载appConfigPath目录下面指定的configFiles配置文件(多个配置文件用,分隔)
     * 加载原理：
     * 如果加了启动参数:global.config.path,就会加载${global.config.path}/appConfigPath下面configFiles[]里面的配置文件
     * 如果没有,就加载${classpath}/appConfigPath下面所有的configFiles[]里面的配置文件
     *
     * @param autoReload    自动动态加载
     * @param appConfigPath 应用的配置目录
     * @param configFiles   配置文件数组
     * @throws ConfigurationException
     */
    private CustomPropertyPlaceholderConfigurer(boolean autoReload, String appConfigPath, String[] configFiles) {
        this.configFiles = configFiles;
        init();/**完成初始化的一些相关操作**/
        loadConfigFiles();
    }


    /**
     * 加载配置文件
     *
     * @throws ConfigurationException
     */
    private void loadConfigFiles()  {
        String basePath = BASE_PATH + File.separator + appConfigPath;
        log.warn("loading ConfigFiles");
        log.warn("basePath:{}",basePath);

        /**如果没有写配置文件,则加载目录下面所有的.properties文件**/
        if (ArrayUtils.isEmpty(configFiles)) {
            File confDirFile = null;
            if (StringUtils.isBlank(appConfigPath)) {
                confDirFile = new File(BASE_PATH);
            } else {
                confDirFile = new File(BASE_PATH, appConfigPath);
            }
            if (!confDirFile.exists()) {
                throw new RuntimeException("Config directory <" + confDirFile.getAbsolutePath() + "> doesn't exists.");
            }
            /**文件名**/
            String[] files = confDirFile.list(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    if (!name.endsWith(".properties")) {
                        return false;
                    }
                    if (name.equals("log4j.properties")) {/**过滤掉log4j的配置文件**/
                        return false;
                    }
                    return true;
                }
            });
            configFiles = files;

        }

        if (ArrayUtils.isNotEmpty(configFiles)) {
            log.warn("configFiles: {}", JsonUtils.toJson(configFiles));
            Configurations configs = new Configurations();

            for (String file : configFiles) {
                PropertiesConfiguration prop = null;
                try {
                    prop = configs.properties(file);
                } catch (ConfigurationException e) {
                    log.error("parse properties file error!", e);
                }
                propList.add(prop);
                props.addConfiguration(prop);
            }
        }
    }


    public String getAppConfigPath() {
        return appConfigPath;
    }

    public boolean isAutoReload() {
        return autoReload;
    }

    /**
     * 获取配置项(返回结果为String)
     *
     * @param key
     * @return
     */
    public String getProperty(String key) {
        return StringUtils.trim(props.getString(key));
    }

    public String getProperty(String key,String defaultValue) {
        return StringUtils.trim(props.getString(key,defaultValue));
    }

    /**
     * 获取Boolean类型的配置项
     * 注:无该配置荐时,返回false
     *
     * @param key
     * @return
     */
    public Boolean getBooleanProperty(String key) {
        return props.getBoolean(key, false);
    }


    /**
     * 获取Integer类型的配置项
     * 注:无该配置荐时,返回new Integer(0)
     *
     * @param key
     * @return
     */
    public Integer getIntegerProperty(String key) {
        return props.getInteger(key, new Integer(0));
    }

    /**
     * 获取Integer类型的配置项
     * 注:无该配置荐时,返回new Integer(0)
     * @param key
     * @return
     */
    public int getIntegerProperty(String key, int defaultValue){
        return props.getInteger(key, defaultValue);
    }

    /**
     * 获取Integer类型的配置项
     * 注:无该配置荐时,返回new Float(0)
     *
     * @param key
     * @return
     */
    public Float getFloatProperty(String key) {
        return props.getFloat(key, new Float(0));
    }


    /**
     * 获取Long类型的配置项
     * 注:无该配置荐时,返回new Long(0)
     *
     * @param key
     * @return
     */
    public Long getLongProperty(String key) {
        return props.getLong(key, new Long(0));
    }


    /**
     * 获取Double类型的配置项
     * 注:无该配置荐时,返回new Double(0)
     *
     * @param key
     * @return
     */
    public Double getDoubleProperty(String key) {
        return props.getDouble(key, new Double(0));
    }


    /**
     * 获取Short类型的配置项
     * 注:无该配置荐时,返回new Short(0)
     *
     * @param key
     * @return
     */
    public Short getShortProperty(String key) {
        return props.getShort(key, Short.valueOf("0"));
    }

    public Short getShortProperty(String key, short defaultValue) {
        return props.getShort(key, defaultValue);
    }


    /**
     * 获取String []类型的配置项
     * 注:配置项各字符之前通过,分隔 eg param =a,b,c,d
     *
     * @param key
     * @return
     */
    public String[] getStringArrayProperty(String key) {
        return props.getStringArray(key);
    }


    /**
     * 获取BigDecimal类型的配置项
     * 注:无该配置荐时,返回new BigDecimal("0")
     *
     * @param key
     * @return
     */
    public BigDecimal getBigDecimalProperty(String key) {
        return props.getBigDecimal(key, new BigDecimal("0"));
    }


    /**
     * 重新加载配置项
     */
    public void reloadProperties() {

    }

    /**
     * 刷新配置项
     *
     * @throws ConfigurationException
     */
    public void refreshProperties() throws ConfigurationException {

    }


    /**
     * 返回构建 PropertyPlaceholderConfigurer(动态加载配置文件)的Builder
     *
     * @return
     */
    public static PropertyPlaceholderBuilder getBuilder() {
        return new PropertyPlaceholderBuilder();
    }

    /**
     * 构建 PropertyPlaceholderConfigurer(动态加载配置文件)
     * PropertyPlaceholderConfigurer这个类参数比较多,固使用Builder模式
     *
     * @author zhangjiawei
     */
    public static class PropertyPlaceholderBuilder {

        /**
         * 应用程序在配置分离下面的子目录
         **/
        private String appConfigPath = "";

        /**
         * 要加载的配置文件,多个配置文件用,分隔,不写则加载目录下面的所有.properties文件
         **/
        private String[] configFiles=new String[0];

        /**
         * 自动动态加载,默认为true
         **/
        private boolean autoReload = true;

        /**
         * 设置应用的配置目录
         *
         * @param appConfigPath 应用的配置目录
         */
        public PropertyPlaceholderBuilder appConfigPath(String appConfigPath) {
            this.appConfigPath = appConfigPath;
            return this;
        }

        /**
         * 设置是否自动动态加载
         *
         * @param autoReload 自动动态加载
         */
        public PropertyPlaceholderBuilder autoReload(boolean autoReload) {
            this.autoReload = autoReload;
            return this;
        }

        /**
         * 设置配置文件,多个配置文件用,分隔
         *
         * @param configFiles
         */
        public PropertyPlaceholderBuilder configFiles(String configFiles) {
            this.configFiles = addConfigFiles(StringUtils.split(configFiles, ","));
            return this;
        }

        /**
         * 设置配置文件数组
         *
         * @param configFiles
         */
        public PropertyPlaceholderBuilder configFiles(String[] configFiles) {
            this.configFiles = addConfigFiles(configFiles);
            return this;
        }

        /**
         * 添加配置文件
         *
         * @param configFiles
         * @return
         */
        private String[] addConfigFiles(String[] configFiles) {
            if (this.configFiles != null && configFiles != null && configFiles.length > 0) {
                return ArrayUtils.addAll(this.configFiles, configFiles);
            } else {
                return this.configFiles;
            }
        }

        /**
         * 生成PropertyPlaceholderConfigurer对象
         *
         * @return
         * @throws ConfigurationException
         */
        public CustomPropertyPlaceholderConfigurer build()  {
            return new CustomPropertyPlaceholderConfigurer(autoReload, appConfigPath, configFiles);
        }
    }


    /******************  main test ****************************/

    public static void main(String[] args) throws ConfigurationException {
            System.out.println(CustomPropertyPlaceholderConfigurer.BASE_PATH);
            CustomPropertyPlaceholderConfigurer conf = CustomPropertyPlaceholderConfigurer.getBuilder().configFiles("tair-conf.properties").build();

            /**
             * -Dglobal.config.path="/env/test"
             *
             **/
            System.err.println(conf.getProperty("tair.appkey.local"));

            Parameters params = new Parameters();

            List<FileLocationStrategy> subs = Arrays.asList(
                    new ProvidedURLLocationStrategy(),
                    new FileSystemLocationStrategy(),
                    new ClasspathLocationStrategy());
            FileLocationStrategy strategy = new CombinedLocationStrategy(subs);

            // Read first file directly via the builder
            FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
                    new FileBasedConfigurationBuilder<PropertiesConfiguration>(PropertiesConfiguration.class)
                            .configure(params.fileBased()
                                    .setFile(new File("config.properties")).setLocationStrategy(strategy));
            PropertiesConfiguration config = builder.getConfiguration();

    }


}


